package jpf.nextsearch.search.impl;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import jpf.nextsearch.base.GenericObjectInterface;
import jpf.nextsearch.base.GlobalConfiguration;
import jpf.nextsearch.ds.dao.ResourceDao;
import jpf.nextsearch.ds.model.ResourceVO;
import jpf.nextsearch.exception.InitializationException;
import jpf.nextsearch.search.ResourceSearcher;
import jpf.nextsearch.search.model.SearchRequest;
import jpf.nextsearch.search.model.SearchResponse;
import jpf.nextsearch.util.Toolkit;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

/**
 * @author <a href="mailto:jacek.pruszkowski@gmail.com">Jacek Pruszkowski</a>
 */
public class ResourceSearcherImpl extends HibernateDaoSupport implements
		ResourceSearcher, GenericObjectInterface {
	private boolean loggingDebug;
	private boolean loggingError;
	private boolean loggingWarning;
	private boolean loggingInfo;
	private GlobalConfiguration globalConfiguration;
	private Logger logger;
	private ResourceDao resourceDao;
	
	public ResourceSearcherImpl() {
		setLogger(Logger.getLogger(this.getClass()));
	}

	@Override
	public SearchResponse searchResources(SearchRequest request) {
		if (isLoggingDebug()) {
			logger.debug("searchResources with request:\n" +Toolkit.objectToString(request));
		}
		
		SearchResponse response = new SearchResponse();

		List<String> words = prepareSearchQuery(request.getQuery());
		// query puste, zwracamy pusty wynik
		if (words == null || words.size() == 0) {
			return response;
		}
		// budujemy wyrazenie like
		StringBuilder likeBuilder = new StringBuilder("%");
		for (int i = 0; i < words.size(); i++) {
			likeBuilder.append(words.get(i) + "%");
		}
		// zapytanie o ilosc wynikow
		String countQuery = "select count(r) " + prepareWhereQueryPart(request);
		Query query = getSession().createSQLQuery(countQuery);
		setQueryParams(query, likeBuilder.toString(), request);
		BigInteger size = (BigInteger) query.uniqueResult();
		long totalCount = size != null ? size.longValue() : 0;
		// nie ma wynikow, zwracamy pusty wynik
		if (totalCount == 0) {
			return response;
		}

		// zapytanie zasoby
		String resourceQuery = "select r.* " + prepareWhereQueryPart(request); //XXXX+ group by host + order by name
		query = getSession().createSQLQuery(resourceQuery).addEntity(ResourceVO.class);
		setQueryParams(query, likeBuilder.toString(), request);
		@SuppressWarnings("unchecked")
		List<ResourceVO> results = (List<ResourceVO>) query.list();
		response.setResults(results);

		return response;
	}
	
	private void setQueryParams(Query query, String like, SearchRequest request) {
		query.setString("name", like);
		
		if (request.getResourceType() != null) {
			query.setInteger("resourceType", request.getResourceType().toInt());
		}
		
		if (request.getResourceServiceType() != null) {
			query.setInteger("resourceServiceType", request.getResourceServiceType().toInt());
		}
		
		query.setFirstResult(request.getResultsStart()).setMaxResults(
				request.getMaxResults());		
	}

	private String prepareWhereQueryPart(SearchRequest request) {
		String query = null;
		
		if (request.isSearchActiveHostsOnly()) {
			query = " from resource r, host h where (h.id = r.id and h.available = true) and ";
		} else {
			query = " from resource r where ";
		}
		
		if (SearchRequest.SEARCH_TYPE_NAME_AND_PATH == request.getSearchType()) {
			query += "(lower(r.name) like :name or lower(r.path) like :name)";
		} else if (SearchRequest.SEARCH_TYPE_PATH_ONLY == request.getSearchType()) {
			query += "(lower(r.path) like :name)";
		} else {
			query += "(lower(r.name) like :name)";
		}
		
		if (request.getResourceType() != null) {
			query += " and (r.resource_type = :resourceType)";
		}
		
		if (request.getResourceServiceType() != null) {
			query += " and (r.service_type = :resourceServiceType)";
		}		

		return query;
	}

	private List<String> prepareSearchQuery(String query) {
		if (StringUtils.isBlank(query)) {
			return null;
		}
		query = query.toLowerCase();
		List<String> prepared = new ArrayList<String>();
		String[] splited = query.split("\\s+");
		for (int i = 0; i < splited.length; i++) {
			if (splited[i].length() > 0) {
				prepared.add(splited[i]);
			}
		}
		return prepared.size() > 0 ? prepared : null;
	}

	public void setResourceDao(ResourceDao resourceDao) {
		this.resourceDao = resourceDao;
	}

	public ResourceDao getResourceDao() {
		return resourceDao;
	}

	/**
	 * @return the loggingDebug
	 */
	public boolean isLoggingDebug() {
		return loggingDebug;
	}

	/**
	 * @param loggingDebug
	 *            the loggingDebug to set
	 */
	public void setLoggingDebug(boolean loggingDebug) {
		this.loggingDebug = loggingDebug;
	}

	/**
	 * @return the loggingError
	 */
	public boolean isLoggingError() {
		return loggingError;
	}

	/**
	 * @param loggingError
	 *            the loggingError to set
	 */
	public void setLoggingError(boolean loggingError) {
		this.loggingError = loggingError;
	}

	/**
	 * @return the loggingWarning
	 */
	public boolean isLoggingWarning() {
		return loggingWarning;
	}

	/**
	 * @param loggingWarning
	 *            the loggingWarning to set
	 */
	public void setLoggingWarning(boolean loggingWarning) {
		this.loggingWarning = loggingWarning;
	}

	/**
	 * @return the loggingInfo
	 */
	public boolean isLoggingInfo() {
		return loggingInfo;
	}

	/**
	 * @param loggingInfo
	 *            the loggingInfo to set
	 */
	public void setLoggingInfo(boolean loggingInfo) {
		this.loggingInfo = loggingInfo;
	}

	/**
	 * @return the globalConfiguration
	 */
	public GlobalConfiguration getGlobalConfiguration() {
		return globalConfiguration;
	}

	/**
	 * @param globalConfiguration
	 *            the globalConfiguration to set
	 */
	public void setGlobalConfiguration(GlobalConfiguration globalConfiguration) {
		this.globalConfiguration = globalConfiguration;
	}

	/**
	 * Get the value of logger
	 * 
	 * @return the value of logger
	 */
	public Logger getLogger() {
		return logger;
	}

	/**
	 * Set the value of logger
	 * 
	 * @param logger
	 *            new value of logger
	 */
	public void setLogger(Logger logger) {
		this.logger = logger;
	}

	public void init() throws InitializationException {
		if (isLoggingInfo()) {
			getLogger()
					.info(
							"Initialising " + getClass().getName()
									+ " object instance");
		}
	}

	public void destroy() {
		if (isLoggingInfo()) {
			getLogger().info(
					"Destroying " + getClass().getName() + " object instance");
		}
	}

}
